﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Xsl;
using System.Xml;
using System.IO;
using System.Web.UI;
using Microsoft.SharePoint;
using System.Xml.Linq;
using Microsoft.SharePoint.Administration;
using System.Net.Mail;
using System.Net;

namespace BIMP
{
    public interface INotificationStrategy
    {
        string RenderHTML();
        string ExecuteTransformation();
        IEnumerable<XElement> GetEMailNodes();
        IEnumerable<MailMessage> GetEmails();
        void SendEmails();


        IEnumerable<string> AllowedBindings { get; }

        List<Func<SPSite, SPWeb, SPList, KeyValuePair<string, object>>> PrepareBinders();
    }

    public class StringWriterWithEncoding : StringWriter
    {
        Encoding encoding;
        public StringWriterWithEncoding(StringBuilder builder, Encoding encoding)
            : base(builder)
        {
            this.encoding = encoding;
        }
        public override Encoding Encoding
        {
            get { return encoding; }
        }
    }

    public class AbstractNotificationStrategy : INotificationStrategy
    {
        protected XslCompiledTransform CompiledTransform
        {
            get;
            private set;
        }

        protected XsltArgumentList ParameterBindings
        {
            get;
            private set;
        }

        protected NotificationEngine Engine { get; private set; }

        IList<string> m_AllowedBindings = new List<string>();
        public IEnumerable<string> AllowedBindings
        {
            get
            {
                return m_AllowedBindings;
            }
        }

        public AbstractNotificationStrategy(NotificationEngine engine)
        {
            Engine = engine;

            PrepareParameterBindings();
        }

        public virtual string RenderHTML()
        {
            string result = ExecuteTransformation();
            return result;
        }

        public virtual string ExecuteTransformation()
        {
            string xml = GetXml();
            
            PrepareCompiledTransform(ParameterBindings, Engine.Configuration.XslTransform);


            StringBuilder sb = new StringBuilder();
            using (TextWriter textWriter = new StringWriterWithEncoding(sb, System.Text.Encoding.UTF8))
            {
                using (HtmlTextWriter writer = new HtmlTextWriter(textWriter))
                using (StringReader srXml = new StringReader(xml))
                using (XmlTextReader xrXml = new XmlTextReader(srXml))
                {
                    XmlWriterSettings settings = new XmlWriterSettings();
                    settings.Encoding = System.Text.Encoding.UTF8;
                    settings.CheckCharacters = true;
                    settings.Indent = true;
                    settings.ConformanceLevel = ConformanceLevel.Fragment;
                    using (XmlWriter xwHtml = XmlWriter.Create(writer, settings))
                        CompiledTransform.Transform(xrXml, this.ParameterBindings, xwHtml);
                }

                textWriter.Flush();
            }

            return sb.ToString();
        }

        public virtual IEnumerable<XElement> GetEMailNodes()
        {
            string transformed = ExecuteTransformation();

            XElement xe = XElement.Parse(transformed);

            return xe.Descendants("EMail");
        }

        private MailAddress GetMailAddressFromXElement(XElement xe)
        {
            try
            {
                MailAddress ma = new MailAddress(xe.Attribute("Address").Value);
                return ma;
            }
            catch (Exception exc)
            {
                return null;
            }
        }

        public virtual IEnumerable<MailMessage> GetEmails()
        {
            IEnumerable<XElement> eMails = GetEMailNodes();

            foreach (var mail in eMails)
            {
                MailMessage message = new MailMessage();
                var from = mail.Element("FROM");
                message.From = GetMailAddressFromXElement(from);

                var to = mail.Element("TO");
                if (to != null)
                    foreach (var r in to.Elements("Recipient"))
                    {
                        var ma = GetMailAddressFromXElement(r);
                        if(ma != null)
                            message.To.Add(ma);
                    }

                var cc = mail.Element("CC");
                if (cc != null)
                    foreach (var r in cc.Elements("Recipient"))
                    {
                        var ma = GetMailAddressFromXElement(r);
                        if(ma != null)
                            message.CC.Add(ma);
                    }
                
                var bcc = mail.Element("BCC");
                if(bcc != null)
                    foreach (var r in bcc.Elements("Recipient"))
                    {
                        var ma = GetMailAddressFromXElement(r);
                        if (ma != null)
                            message.Bcc.Add(ma);
                    }

                var subject = mail.Element("SUBJECT");
                message.Subject = subject.Value;

                var body = mail.Element("BODY");
                message.IsBodyHtml = Convert.ToBoolean(body.Attribute("IsHtml").Value);
                message.Body = body.Value;
                message.BodyEncoding = message.SubjectEncoding = System.Text.Encoding.UTF8;

                yield return message;
            }
        }

        public virtual void SendEmails()
        {
            var mailMessages =  GetEmails();

            foreach (var msg in mailMessages)
                SendEmail("10.10.100.29", msg);
        }

        private void PrepareParameterBindings()
        {
            ParameterBindings = new XsltArgumentList();

            //add configuration based bindings
            ParameterBindings.AddParam("listId", "", Engine.Configuration.ListId.ToString("B"));
            m_AllowedBindings.Add("listId");
            //ParameterBindings.AddParam("optSkipItemIterator", "", Engine.Configuration.SkipItemIterator);
            //m_AllowedBindings.Add("optSkipItemIterator");
            ParameterBindings.AddParam("webId", "", Engine.Configuration.WebId.ToString("B"));
            m_AllowedBindings.Add("webId");
            ParameterBindings.AddParam("siteId", "", Engine.Configuration.SiteId.ToString("B"));
            m_AllowedBindings.Add("siteId");

            //ParameterBindings.AddParam("emailMarkUpHeader", "", Engine.Configuration.EMailHeader);
            //m_AllowedBindings.Add("emailMarkUpHeader");
            //ParameterBindings.AddParam("emailMarkUpFooter", "", Engine.Configuration.EMailFooter);
            //m_AllowedBindings.Add("emailMarkUpFooter");


            //add context specific bindings
            using (SPSite eSite = new SPSite(Engine.Configuration.SiteId))
            using (SPWeb eWeb = eSite.OpenWeb(Engine.Configuration.WebId))
            {
                SPList list = eWeb.Lists[Engine.Configuration.ListId];

                foreach (var binder in PrepareBinders())
                {
                    KeyValuePair<string, object> kvp = binder(eSite, eWeb, list);

                    m_AllowedBindings.Add(kvp.Key);

                    ParameterBindings.AddParam(kvp.Key, "", kvp.Value);
                }
            }
        }
        private void PrepareCompiledTransform(XsltArgumentList argList, string strXsl)
        {
            using (StringReader srXsl = new StringReader(strXsl))
            using (XmlTextReader xrXsl = new XmlTextReader(srXsl))
            {
                XsltSettings sts = new XsltSettings(true, true);
                CompiledTransform = new XslCompiledTransform();
                CompiledTransform.Load(xrXsl, sts, null);
            }
        }

        public string GetXml()
        {
            string xml = string.Empty;

            string listDataUrl = this.Engine.Configuration.DataSourceCFG;
                /*string.Format("{0}_vti_bin/ListData.svc/{1}{2}", 
                Engine.TargetList.ParentWeb.Url,
                Engine.TargetList.Title,
                "");*/

            WebClient wc = new WebClient();
            wc.UseDefaultCredentials = true;
            wc.Encoding = System.Text.Encoding.UTF8;
            xml = wc.DownloadString(listDataUrl);
            
            //xml = Engine.TargetList.Items.Xml;
            return xml;
        }


        public virtual List<Func<SPSite, SPWeb, SPList, KeyValuePair<string, object>>> PrepareBinders()
        {
            List<Func<SPSite, SPWeb, SPList, KeyValuePair<string, object>>> ParameterBinders = new List<Func<SPSite, SPWeb, SPList, KeyValuePair<string, object>>>();

            ParameterBinders.Add((s, w, l) => new KeyValuePair<string, object>("siteUrl", w.Url));
            ParameterBinders.Add((s, w, l) => new KeyValuePair<string, object>("listImageUrl", w.Url + l.ImageUrl));
            ParameterBinders.Add((s, w, l) => new KeyValuePair<string, object>("listTitle", l.Title));
            ParameterBinders.Add((s, w, l) => new KeyValuePair<string, object>("listUrl", s.Url + "/" + l.RootFolder.Url.Replace(" ", "%20")));
            ParameterBinders.Add((s, w, l) => new KeyValuePair<string, object>("listDefaultViewUrl", l.GetDefaultViewServerUrl()));

            return ParameterBinders;
        }

        /// <summary>
        /// This method filters or sets and mail addresses that are related to items
        /// </summary>
        /// <param name="mailToAddresses"></param>
        /// <param name="item"></param>
        /// <returns></returns>
        protected List<MailAddress> GetMailAddresses(string[] mailToAddresses, SPListItem item, SPList list, bool allowEmptyValues)
        {
            if ((mailToAddresses == null || mailToAddresses.Length == 0) && !allowEmptyValues)
                throw new Exception("Email can not be sent when there are no emails supplied.");
            List<MailAddress> addresses = new List<MailAddress>();

            if (mailToAddresses == null)
                return addresses;

            foreach (string strAddress in mailToAddresses)
            {
                if (strAddress.StartsWith("[#") && strAddress.EndsWith("#]"))
                {
                    if (item == null)
                        continue;

                    List<MailAddress> mas = GetMailAddress(strAddress, item, list);
                    if (mas != null)
                        //addresses.Add(ma);
                        addresses.AddRange(mas);
                }
                else
                    addresses.Add(new MailAddress(strAddress));
            }

            return addresses;
        }
        protected List<MailAddress> GetMailAddress(string strAddress, SPListItem item, SPList list)
        {
            List<MailAddress> ret = new List<MailAddress>();
            try
            {

                string userName = string.Empty;
                int id = -1;
                string fieldName = strAddress.Substring(2, strAddress.Length - 2);
                fieldName = fieldName.Substring(0, fieldName.Length - 2);

                if (fieldName.ToLower().Equals("checkoutuser"))
                {
                    if (item.File != null && item.File.CheckedOutBy != null)
                        ret.Add(new MailAddress(item.File.CheckedOutBy.Email));
                }
                else
                {
                    #region find field
                    SPField field = null;
                    foreach (SPField f in list.Fields)
                    {
                        if (string.Compare(f.InternalName, fieldName, true) == 0
                            || string.Compare(f.Title, fieldName, true) == 0)
                        {
                            field = f;
                            break;
                        }
                    }

                    if (field == null)
                        return null;
                    #endregion find field
                    string val = Convert.ToString(item[field.Id]);

                    if (string.IsNullOrEmpty(val))
                        return null;

                    using (SPWeb web = list.ParentWeb)
                    {
                        SPFieldUserValueCollection uvc = new SPFieldUserValueCollection(web, val);
                        string emails = string.Empty;
                        foreach (SPFieldUserValue uv in uvc)
                        {
                            string email = uv.User.Email;
                            if (String.IsNullOrEmpty(email))
                                continue;
                            ret.Add(new MailAddress(email));
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                //Logging.ServiceLog.LogException(ex);
                return ret;
            }
            return ret;
        }
        protected List<MailAddress> GetDistinctMailAddress(List<MailAddress> src)
        {
            List<MailAddress> ret = new List<MailAddress>();
            foreach (MailAddress ma in src)
                if (!ret.Contains(ma))
                    ret.Add(ma);

            return ret;
        }

        protected void SendEmail(string ServerIP, MailMessage msg)
        {
            SmtpClient smtp = new SmtpClient(ServerIP);//WebApplication.OutboundMailServiceInstance.Server.Address);

            smtp.UseDefaultCredentials = (smtp.Credentials == null);
            smtp.Send(msg);
        }
        protected void SendEmail(string ServerIP, MailAddress from, List<MailAddress> masTo, List<MailAddress> masCC, List<MailAddress> masBCC, string subject, string body)
        {
            using (MailMessage mail = new MailMessage())
            {
                mail.From = from;
                foreach (MailAddress ma in masTo)
                    mail.To.Add(ma);
                foreach (MailAddress ma in masCC)
                    mail.CC.Add(ma);
                foreach (MailAddress ma in masBCC)
                    mail.Bcc.Add(ma);
                mail.Subject = subject;
                mail.IsBodyHtml = true;
                mail.Body = body;

                SendEmail(ServerIP, mail);
            }
        }
    }

    public class ItemNotificationStrategy : AbstractNotificationStrategy
    {
        public ItemNotificationStrategy(NotificationEngine engine) : base(engine)
        {
        }

        public override string RenderHTML()
        {
            throw new Exception("Noy Yet Implemented");
        }

        public override void SendEmails()
        {
            throw new Exception("Noy Yet Implemented");
        }
    }


    public class NotificationEngine
    {

        public RmdConfiguration Configuration
        {
            get;
            private set;
        }

        public INotificationStrategy Strategy { get; private set; }

        public SPList TargetList { get; private set; }

        public NotificationEngine(RmdConfiguration cfg)
        {
            Configuration = cfg;

            using (SPSite eSite = new SPSite(Configuration.SiteId))
            using (SPWeb eWeb = eSite.OpenWeb(Configuration.WebId))
            {
                TargetList = eWeb.Lists[Configuration.ListId];
            }


            SelectAppropriateStrategy();
        }

        private void SelectAppropriateStrategy()
        {

            Strategy = new AbstractNotificationStrategy(this);

            return;

            //switch (Configuration.Type)
            //{
            //    case RmdType.Item:
            //        Strategy = new ItemNotificationStrategy(this);
            //        break;
            //    case RmdType.Bulk:
            //        Strategy = new BulkNotificationStrategy(this);
            //        break;
            //    case RmdType.Summary:
            //        Strategy = new SummaryNotificationStrategy(this);
            //        break;
            //    default:
            //        throw new Exception("Strategy undefined");
            //}

        }

        public string RenderHtml()
        {
            return Strategy.RenderHTML();
        }

        public void SendEmails()
        {
            Strategy.SendEmails();
        }
    }
}
